﻿using System;
using System.Collections.Generic;
using System.Text;
using DO.Clustering;

namespace BLL.Relations.VectorDistances
{
    /// <summary>
    /// the Tanimoto coefficient is a measure of the similarity of two sets. 
    /// it is used to calculate how similar two items are based on the lists of 
    /// properties
    /// If you have two sets, A and B where 
    ///     A={shirt,shoes,pants,socks}
    ///     B={shirt,skirt,shoes}
    /// then the intersection (overlapping) set, which I'll call C is {shirt,shoes}. 
    /// The Tanimoto coefficient is 2/(4+3-2)=0.4
    /// </summary>
    public class TanimotoCoefficientCalculator:IDistanceCalculator
    {
        #region IDistanceCalculator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
                FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedFeatureIDs=new List<int>();
            foreach(int FeatureID1 in FeatureFreq1.Keys)
            {
                if(FeatureFreq2.ContainsKey(FeatureID1))
                {
                    sharedFeatureIDs.Add(FeatureID1);
                    contributingFeatures.Add(FeatureID1, Math.Min(FeatureFreq1[FeatureID1].Count, FeatureFreq2[FeatureID1].Count));
                }
            }
            double tanimotoDistance = 1.00 - (double) sharedFeatureIDs.Count/(FeatureFreq1.Count + FeatureFreq2.Count - sharedFeatureIDs.Count);
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoCommonFeatureScale(
                    FeatureFreq1.Count, FeatureFreq2.Count,
                    contributingFeatures.Count);
            return tanimotoDistance*contributingFeatureScale;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="FeatureWeights"></param>
        /// <param name="useNGramSimilarity"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            Dictionary<int, double> FeatureWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
               FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedFeatureIDs = new List<int>();
            Dictionary<int, Dictionary<int, double>> featureSimilarityMatrix =
                FeatureSimilarityMatrix.BuildFeatureDistanceMatrixUsingNgram(
                    FeatureFreq1, FeatureFreq2);
            List<int> unionFeatureIDs=new List<int>();
            foreach(int FeatureID in FeatureFreq1.Keys)
            {
                if(!unionFeatureIDs.Contains(FeatureID))
                    unionFeatureIDs.Add(FeatureID);
            }
            foreach(int FeatureID in FeatureFreq2.Keys)
            {
                if(!unionFeatureIDs.Contains(FeatureID))
                    unionFeatureIDs.Add(FeatureID);
            }

            foreach (int FeatureID in unionFeatureIDs)
            {
                if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                {
                    sharedFeatureIDs.Add(FeatureID);
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
                else
                {
                    if (useNGramSimilarity && ngramLen>0)
                    {
                        Dictionary<int, double> similarFeatures = featureSimilarityMatrix[FeatureID];
                        foreach (int similarFeatureID in similarFeatures.Keys)
                        {
                            if(FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(similarFeatureID))
                            {
                                if(!sharedFeatureIDs.Contains(FeatureID))
                                    sharedFeatureIDs.Add(FeatureID);
                            }
                            else if(FeatureFreq2.ContainsKey(FeatureID) && FeatureFreq1.ContainsKey(similarFeatureID))
                            {
                                if(!sharedFeatureIDs.Contains(FeatureID))
                                    sharedFeatureIDs.Add(FeatureID);
                            }
                        }
                    }
                }
            }
            double tanimotoDistance = 1.00 - (double)sharedFeatureIDs.Count / (FeatureFreq1.Count + FeatureFreq2.Count - sharedFeatureIDs.Count);

            //Dictionary<int, double> contributingFeatureWeights = new Dictionary<int, double>();
            //foreach (int sharedFeatureID in contributingFeatures.Keys)
            //{
            //    if (FeatureWeights.ContainsKey(sharedFeatureID))
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, FeatureWeights[sharedFeatureID]);
            //    }
            //    else
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, 0);
            //    }
            //}
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);

            return tanimotoDistance*contributingFeatureScale;
        }

        public double CalculateDistance(Dictionary<int, FeatureFreq> FeatureFreq1, Dictionary<int, FeatureFreq> FeatureFreq2,
                                        Dictionary<int, double> FeatureWeights, Dictionary<int, string> FeatureStemmings1,
                                        Dictionary<string, List<int>> FeatureStemmings2,
                                        ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null ||
              FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            List<int> sharedFeatureIDs = new List<int>();
            
            List<int> unionFeatureIDs = new List<int>();
            foreach (int FeatureID in FeatureFreq1.Keys)
            {
                if (!unionFeatureIDs.Contains(FeatureID))
                    unionFeatureIDs.Add(FeatureID);
            }
            foreach (int FeatureID in FeatureFreq2.Keys)
            {
                if (!unionFeatureIDs.Contains(FeatureID))
                    unionFeatureIDs.Add(FeatureID);
            }

            foreach (int FeatureID in unionFeatureIDs)
            {
                if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                {
                    sharedFeatureIDs.Add(FeatureID);
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
                else
                {
                    List<int> sharedFeatureIDsAll = FeatureStemmings2[FeatureStemmings1[FeatureID]];
                    List<int> sharedFeatureIDs1 = new List<int>();
                    List<int> sharedFeatureIDs2 = new List<int>();
                    foreach (int FeatureID1 in FeatureFreq1.Keys)
                    {
                        if (sharedFeatureIDsAll.Contains(FeatureID1))
                        {
                            sharedFeatureIDs1.Add(FeatureID1);
                        }
                    }
                    foreach (int FeatureID2 in FeatureFreq2.Keys)
                    {
                        if (sharedFeatureIDsAll.Contains(FeatureID2))
                        {
                            sharedFeatureIDs2.Add(FeatureID2);
                        }
                    }

                    if (sharedFeatureIDs1.Count>0 && sharedFeatureIDs2.Count>0)
                    {
                        foreach (int similarFeatureID in sharedFeatureIDsAll)
                        {
                            if (!sharedFeatureIDs.Contains(similarFeatureID))
                                sharedFeatureIDs.Add(similarFeatureID);
                        }
                    }
                }
            }
            double tanimotoDistance = 1.00 - (double)sharedFeatureIDs.Count / (FeatureFreq1.Count + FeatureFreq2.Count - sharedFeatureIDs.Count);

            //Dictionary<int, double> contributingFeatureWeights = new Dictionary<int, double>();
            //foreach (int sharedFeatureID in contributingFeatures.Keys)
            //{
            //    if (FeatureWeights.ContainsKey(sharedFeatureID))
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, FeatureWeights[sharedFeatureID]);
            //    }
            //    else
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, 0);
            //    }
            //}
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);

            return tanimotoDistance * contributingFeatureScale;
        }

        #endregion
    }
}
